package com.aabte.springcloudlearn.ribbonhystrixcustomerdemo.service;

import com.aabte.springcloudlearn.common.beans.User;
import com.aabte.springcloudlearn.common.utils.CommonMethod;
import com.aabte.springcloudlearn.ribbonhystrixcustomerdemo.hystrix.UserCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.command.AsyncResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Future;

/**
 * @author Daniel
 */
@Slf4j
@Service
public class RibbonHystrixCustomerService {

    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(
            //缺省为方法名
            commandKey = "helloCommand",
            //缺省为类名
            threadPoolKey = "helloPool",
            //指定降级方法，在熔断和异常时会走降级方法
            fallbackMethod = "helloHystrixFallback",
            commandProperties = {
                    //超时时间
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000"),
                    // 融断阀值
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
            },
            threadPoolProperties = {
                    //并发，缺省为10
                    @HystrixProperty(name = "coreSize", value = "5")
            }
    )
    public String hello() {
        log.info("Start hello.");

        long st = System.currentTimeMillis();

        // 设置随机3秒内延迟，hystrix默认延迟2秒未返回则熔断，调用回调方法
        sleep(3000);

        String result = restTemplate.getForEntity("http://hello-service/hello", String.class).getBody();

        long et = System.currentTimeMillis();

        log.info("Spend time: {}", (et - st));

        return result;
    }

    /**
     * {@link #hello} Hystrix降级方法
     * 注意，降级方法的返回类型与形参要与原方法相同，
     * 可以多一个{@link Throwable}参数放到最后，用来获取异常信息
     *
     * @param ex 异常对象
     * @return 降级后的结果，降级方法，状态码返回503
     */
    public String helloHystrixFallback(Throwable ex) {
        log.warn("Invoke helloHystrixFallbackByThrowable. ex: {}", CommonMethod.getErrorStack(ex));
        return "helloHystrixFallbackByThrowable! ex: " + CommonMethod.getErrorStack(ex);
    }


    /**
     * 使用自定义HystrixCommand同步方法调用接口
     */
    public User useSyncRequestGetUserById(Long userId) {
        log.info("Start useSyncRequestGetUserById. userId: {}", userId);
        User userSync = new UserCommand(com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(
                HystrixCommandGroupKey.Factory.asKey("")
        ).andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(5000)),
                restTemplate, userId).execute();
        log.info("End useSyncRequestGetUserById. userSync: {}", userSync);
        return userSync;
    }

    /**
     * 使用自定义HystrixCommand异步方法调用接口
     */
    public User useAsyncRequestGetUserById(Long userId) {
        log.info("Start useAsyncRequestGetUserById. userId: {}", userId);
        Future<User> userFuture = new UserCommand(com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(
                HystrixCommandGroupKey.Factory.asKey("")
        ).andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(5000)),
                restTemplate, userId).queue();

        User userAsync = null;
        try {
            userAsync = userFuture.get();
        } catch (Exception e) {
            log.error(CommonMethod.getErrorStack(e));
        }

        log.info("End useAsyncRequestGetUserById. userAsync: {}", userAsync);
        return userAsync;
    }


    /**
     * 使用注解实现异步请求调用
     * 注意：此处{@link AsyncResult}为netflix实现，Spring也做了实现，注意导包
     */
    @HystrixCommand(fallbackMethod = "fallbackForUseAnnotationAsyncRequestGetUserById")
    public Future<User> useAnnotationAsyncRequestGetUserById(Long userId) {
        log.info("Start useAnnotationAsyncRequestGetUserById. userId: {}", userId);

        Map<String, Object> urlParams = new HashMap<>(1);
        urlParams.put("userId", userId);

        AsyncResult<User> asyncResult = new AsyncResult<User>() {
            @Override
            public User invoke() {
                log.info("Start AsyncResult.invoke method. urlParams: {}", urlParams);
                User user = restTemplate.getForObject("http://hello-service/user/{userId}", User.class, urlParams);
                log.info("End AsyncResult.invoke method. user: {}", user);
                return user;
            }
        };

        log.info("End useAnnotationAsyncRequestGetUserById.");
        return asyncResult;
    }

    /**
     * {@link #useAnnotationAsyncRequestGetUserById(Long)} 降级方法
     */
    public User fallbackForUseAnnotationAsyncRequestGetUserById(Long userId, Throwable e) {
        log.warn("Start fallbackForUseAnnotationAsyncRequestGetUserById. userId: {}, ex: {}", userId, CommonMethod.getErrorStack(e));
        return null;
    }


    private static void sleep(int maxSleepMillis) {
        int sleepMillis = new Random().nextInt(maxSleepMillis);
        log.info("sleepMillis: {}", sleepMillis);
        try {
            Thread.sleep(sleepMillis);
        } catch (InterruptedException e) {
            log.error(CommonMethod.getErrorStack(e));
        }
    }

}
